BiblioRhone

Chapitre introductif d’un ouvrage sur la recherche sur le Rhône (20 ans de ZABR-OHM). Création de:

Code
# Chargement des packages et des données
library(tidyverse)
library(magrittr)
library(bibliometrix)
library(ggraph)
#library(tidygraph)
library(tidytext)
library(mixr)
library(bibou)
set.seed(123)

Import et nettoyage de la base .bib (exporté depuis Web of Science).

Dans titre ou topic: “Rhone” & “river|basin|catchment”. Filtre ultérieur pour retirer les mentions de Rhone présentes seulement dans les “Keywords +” (issus d’un algorithme de Web of Science reposant en partie sur les références biblio des documents)

Code
if(!file.exists("data/M.RDS")){
  bibou::bib_remove_duplicates("data-raw/savedrecs_Rhone.bib")
  M=bibliometrix::convert2df(file = "data-raw/savedrecs_Rhone.bib",
                  dbsource = "isi",
                  format = "bibtex")
  tib_doc=bibou::bib_tib_doc("data-raw/savedrecs_Rhone_clean.bib",
                           check_in_text="RHONE") %>% 
    mutate(condition=PY>2018 & str_detect(AU, "ARNAUD F")) %>% 
    mutate(AU=case_when(condition~str_replace_all(AU,"ARNAUD F","ARNAUD Fa"),
                        TRUE~AU),
           id_doc=case_when(condition~str_replace_all(id_doc,"ARNAUD F","ARNAUD Fa"),
                            TRUE~id_doc))
  saveRDS(M,"data/M.RDS")
  saveRDS(tib_doc,"data/tib_doc.RDS")
}
M=readRDS("data/M.RDS")
tib_doc=readRDS("data/tib_doc.RDS")
Code
if(!file.exists("data/tib_doc_AU.RDS")){
  tib_doc_AU=bibou::bib_tib_doc_AU(tib_doc)
  tib_doc_CR=bibou::bib_tib_doc_CR(tib_doc)
  tib_doc_DE=bibou::bib_tib_doc_DE(tib_doc)
  tib_doc_ABW=bibou::bib_tib_doc_ABW(tib_doc)
  saveRDS(tib_doc_AU,"data/tib_doc_AU.RDS")
  saveRDS(tib_doc_CR,"data/tib_doc_CR.RDS")
  saveRDS(tib_doc_DE,"data/tib_doc_DE.RDS")
  saveRDS(tib_doc_ABW,"data/tib_doc_ABW.RDS")
}
tib_doc_AU=readRDS("data/tib_doc_AU.RDS")
tib_doc_CR=readRDS("data/tib_doc_CR.RDS")
tib_doc_DE=readRDS("data/tib_doc_DE.RDS")
tib_doc_ABW=readRDS("data/tib_doc_ABW.RDS")

Analyses simples, “à plat”

Documents

Auteurs

On attribue au \(k\)-ième auteur d’un document qui en compte \(n\) en tout un poids \(w\) de

\[w=(n-k+1)\frac{2}{n(n+1)}\] soit par exemple pour un document qui compte 4 auteurs des poids de 0.4,0.3,0.2 et 0.1 pour le premier, deuxième, troisième et quatrième auteur respectivement.

Code
auteurs=tib_doc_AU %>% 
  group_by(AU) %>% 
  summarise(ndoc=n(),
            ndocw=sum(AU_weight),
            TC=sum(TC),
            TCw=sum(TC*AU_weight),
            LC=sum(LC),
            LCw=sum(LC*AU_weight),.groups="drop")
filter
function (.data, ..., .by = NULL, .preserve = FALSE) 
{
    check_by_typo(...)
    by <- enquo(.by)
    if (!quo_is_null(by) && !is_false(.preserve)) {
        abort("Can't supply both `.by` and `.preserve`.")
    }
    UseMethod("filter")
}
<bytecode: 0x5588ea267978>
<environment: namespace:dplyr>

Le corpus compte 4046 auteurs distincts. Un auteur est associé à en moyenne 1.6950074 documents du corpus. 1024 sont associés à au moins 2 documents, 215 sont associés à au moins 5 documents.

  • ndoc nombre de documents (dans ce corpus):
  • TC nombre de citations (globales)
  • LC nombre de citations locales (par les autres articles du corpus)
Code
ggplot(auteurs, aes(x=ndoc)) +
  geom_bar()

Code
p1=mixr::plot_frequencies(auteurs %>% arrange(desc(ndocw)) %>% head(20),
                       AU,freq=ndocw)
p2=mixr::plot_frequencies(auteurs %>% arrange(desc(TCw)) %>% head(20),
                       AU,freq=TCw)
p3=mixr::plot_frequencies(auteurs %>% arrange(desc(LCw)) %>% head(20),
                       AU,freq=LCw)
ggpubr::ggarrange(p1,p2,p3,nrow=1)

Sources

Code
sources=mixr::tidy_frequencies(tib_doc,SO,top_freq=20)

Références

Top 100 des articles les plus cités par le corpus:

Code
citations=tib_doc_CR %>% 
  mixr::tidy_frequencies(CR,top_freq=100)
reactable::reactable(citations,
                     filterable=TRUE)

Nombre de documents par année (dans le corpus)

Code
ggplot(tib_doc,
       aes(x=PY))+
  geom_bar()+
  xlab("année de publication") + ylab("nombre de publications")

Comparaison à deux autres corpora

Code
if(!file.exists("data-raw/tib_doc_Rhine.RDS")){
  for (river in c("Rhine","Rhone","Danube")){
    file <- glue::glue("data-raw/savedrecs_{river}_clean.bib")
    M <- convert2df(file = file,
                    dbsource = "isi",
                    format = "bibtex")
    tib_doc=M %>%
      rownames_to_column(var="id_doc") %>% 
      as_tibble() %>% 
      tidyr::unite(texts,TI,AB,DE,sep="; ",remove=FALSE) %>%
      mutate(rightriver=str_detect(texts,toupper(river))) %>%
      filter(rightriver) %>%
      select(-rightriver,-texts) %>%
      unique()
    saveRDS(tib_doc,glue::glue("data-raw/tib_doc_{river}.RDS"))
  }
}

tib_nb_publ_per_year=
  bind_rows(readRDS("data-raw/tib_doc_Rhone.RDS") %>% mutate(river="Rhone") %>% select(river,PY),
            readRDS("data-raw/tib_doc_Danube.RDS") %>% mutate(river="Danube") %>% select(river,PY),
            readRDS("data-raw/tib_doc_Rhine.RDS") %>% mutate(river="Rhine") %>% select(river,PY)) %>% 
  mutate(period=cut(PY,breaks=c(1940,2000,2011,2023),dig.lab=4)) %>% 
  mutate(period=as.factor(period)) %>% 
  mutate(ntot=n()) %>% 
  group_by(river) %>% 
  mutate(ndocriver=n()) %>% 
  ungroup() %>% 
  group_by(period) %>% 
  group_by(PY) %>% 
  mutate(ndocPY=n()) %>% 
  ungroup() %>% 
  mutate(predprop=ntot*(ndocriver/ntot)*(ndocPY/ntot)) %>% 
  arrange(river,PY) %>% 
  group_by(river,PY,predprop) %>% 
  summarise(n=n(),.groups="drop")
ggplot(tib_nb_publ_per_year,
       aes(x=PY,y=n))+
  geom_path(aes(y=predprop))+
  geom_col(alpha=0.5)+
  xlab("année de publication")+ylab("nombre de publications")+
  facet_grid(rows=vars(river),scales="free_y")+
  scale_x_continuous(limits=c(1980,2024))

Focus sur les SHS

Quels sont les documents qui font mention des termes SOCIAL/SOCIETY/SOCIO-quelque-chose dans leur abstract?

Code
tib_doc =tib_doc %>% 
              mutate(mention_SHS=str_detect(AB,"SOCIAL|SOCIETY|SOCIO")) 
tib_doc_ABW=tib_doc_ABW %>% 
  left_join(tib_doc %>% 
              select(id_doc,mention_SHS),
            by="id_doc")

prop_SHS=tib_doc %>% 
  group_by(mention_SHS) %>%
  tally()
prop_SHS
# A tibble: 3 × 2
  mention_SHS     n
  <lgl>       <int>
1 FALSE        1497
2 TRUE           63
3 NA             91

Peu de documents étiquetés “SHS” a priori (4.04% des documents pour lesquels on dispose de l’abstract) mais ce premier ensemble va nous permettre de rechercher les termes spécifiques aux SHS et d’identifier un “courant SHS” plus important et transverse (cf partie Section 3.1)

Graphes biblio

Réseaux d’auteurs

On définit des communautés d’auteurs en se basant sur les collaborations (co-signature de documents).

Code
nw_coll_auth <- biblioNetwork(M,
                           analysis = "collaboration",
                           network = "authors",
                           sep = ";")
Code
set.seed(12345)
p=networkPlot(nw_coll_auth,
            Title = "Collaborations",
            type = "fruchterman",
            cluster="louvain",
            size=5,
            size.cex=T,
            labelsize=0.5,
            label.n=30,
            label.cex=F,
            alpha=0.5,
            #remove.isolates=TRUE,
            #edges.min=1,
            verbose=FALSE,
            community.repulsion=0)
Code
auteurs=auteurs %>%
  mutate(au=tolower(AU)) %>% 
  left_join(p$cluster_res,by=c("au"="vertex")) %>% 
  mutate(cluster=paste0("cluster",str_pad(cluster,4)))

tib_cluster_commus=auteurs %>% 
  group_by(cluster) %>% 
  summarise(sum_ndocw=sum(ndocw),
            sum_ndoc=sum(ndoc)) %>% 
  arrange(desc(sum_ndocw)) %>% 
  mutate(rank=1:n()) %>% 
  mutate(community=case_when(rank<=12~paste0("c",str_pad(1:n(),pad="0",width=2)),
                             TRUE~NA_character_))

auteurs=auteurs %>% 
  left_join(tib_cluster_commus %>%
              select(cluster,community),
            by="cluster")

A ce stade, de nombreuses communautés (auteurs$cluster %>% unique() %>% length()) sont définies. Pour la suite de l’analyse on ne conservera que les communautés à l’origine des 12 plus grands ensembles de documents (ndoc > 145 et ndocw > 30)

Rattachement des articles à une communauté

Dans le cas où la majorité des auteurs ne fait pas partie d’une communauté définie, on assigne le document à la communauté majoritaire (par exemple si 30% des auteurs font partie de la communauté Cl01 et 70% ne font pas partie d’une communauté définie on assigne le document à la communauté Cl01).

Code
tib_doc_commus=tib_doc_AU %>% 
  select(id_doc,AU,AU_rank,AU_weight) %>%
  left_join(auteurs %>% select(AU,community),by="AU") %>%
  group_by(id_doc,community) %>% 
  summarise(weight=sum(AU_weight,na.rm=TRUE),.groups="drop")
tib_doc_commus=tib_doc %>% 
  left_join(tib_doc_commus,by=c("id_doc"))
Code
tib_commus_prod= tib_doc_commus %>% 
  group_by(community) %>% 
  summarise(neqdoc=sum(weight),
            ndoc=n())
reactable::reactable(tib_commus_prod,pagination=FALSE)

Table des auteurs

Code
auteurs=auteurs
reactable::reactable(auteurs %>% arrange(community),
                     groupBy="community",
                     sortable=TRUE,
                     filterable=TRUE,
                     pagination=FALSE,
                     paginateSubRows=TRUE)

Table des documents*communauté

Code
reactable::reactable(tib_doc_commus %>% select(-id_doc),
                     filterable=TRUE,
                     resizable=TRUE)

Productivité des communautés d’auteurs au cours du temps

Code
tib_community_PY=tib_doc_commus%>% 
  group_by(community,PY) %>% 
  summarise(sumw=sum(weight)) %>% 
  na.omit()
ggplot(tib_community_PY, aes(x=PY, y=sumw))+
  geom_col()+
  facet_wrap(facets=vars(community))+
  xlab("année de publication")+
  ylab("nombre de publications")

Graphe des communautés d’auteurs

Code
liste_auteurs_graph=auteurs %>% 
  filter(!is.na(community)) %>% 
  pull(AU)

ndoc_auteurs_graph=tib_doc_AU %>% 
  filter(AU %in% liste_auteurs_graph) %>% 
  pull(id_doc) %>% unique() %>% 
  length()

Le graphe ci-après représente les collaborations des 2060 auteurs qui ont été rattachés à une communauté que l’on a conservé pour la suite des analyses.

Ces 2060 auteurs représentent 50.91% des auteurs du corpus, mais 60% des documents du corpus ont au moins un de ces auteurs comme n-ième auteur.

Code
auteurs_keep=auteurs %>%
  select(au,community,ndoc) %>% 
  mutate(keep=ndoc>15) %>% 
  group_by(community) %>%
  arrange(desc(ndoc)) %>% 
  mutate(rank=1:n()) %>% 
  mutate(keep=rank<=3) %>% 
  mutate(showname=case_when(keep~au,
                            !keep~NA_character_))
set.seed(1234)
tidyg=tidygraph::as_tbl_graph(p$graph) %>% 
  tidygraph::activate(nodes) %>%
  select(-community) %>% 
  filter(name %in% tolower(liste_auteurs_graph)) %>% 
  left_join(auteurs_keep,by=c("name"="au"))
ggraph(tidyg,layout="fr")+
  geom_edge_link(color="light grey")+
  geom_node_point(aes(color=community,size=ndoc))+
  geom_node_text(aes(label=showname),size=2)

Spécificités des mots-clés par communauté d’auteurs

Code
kw_communities=tib_doc_commus%>% 
  unnest_tokens(output="kw",input="DE",token=stringr::str_split, pattern = "; ") 
spec_com=tidy_specificities(kw_communities,cat1=kw,cat2=community,top_spec=20) %>% 
  arrange(community,desc(spec))
plot_specificities(spec_com,kw,community)

Les journaux associés aux différentes communautés permettent également de les caractériser. Ci-dessous, les publications scientifiques spécifiques (significatif au seuil de 1% i.e. score de spécificité >2).

Code
spec_SO=tidy_specificities(tib_doc_commus,cat1=SO,cat2=community,min_spec=2) %>% 
  arrange(community,desc(spec)) %>% 
  mutate(rank=n():1) %>% 
  select(community, SO, spec, n,rank)

ggplot(spec_SO %>% filter(spec>2),aes(x=fct_reorder(SO,rank), y=spec))+
  geom_col(aes(fill=community))+
  geom_text(aes(y=0,label=SO),hjust=0)+
  coord_flip()+
  theme(axis.text.y=element_blank())

Thématiques

Spécificités du sous-corpus SHS

Voici les termes les plus spécifiques des abstracts contenant “SOCIETY/SOCIAL/SOCIO”:

Code
library(mixr)
tib_doc_ABW=tib_doc_ABW %>%
  mutate(mention_SHS=as.character(mention_SHS))
spec_SHS=tidy_specificities(tib_doc_ABW,
                   cat1=ABW,
                   cat2=mention_SHS) %>% 
  filter(mention_SHS=="TRUE") %>% 
  select(-mention_SHS) %>% 
  arrange(desc(spec))
reactable::reactable(spec_SHS %>% head(100))
Code
scores_SHS= spec_SHS %>% 
  mutate(spec=case_when(spec<2~0,
                        TRUE~spec)) %>% 
  select(ABW,SHS_spec=spec)
tib_doc_ABW=tib_doc_ABW %>% 
  left_join(scores_SHS, by="ABW")
tib_doc_SHS=tib_doc_ABW %>% 
  group_by(id_doc) %>% 
  summarise(SHS_spec=sum(SHS_spec,na.rm=TRUE),.groups="drop")
tib_doc_commus_SHS=tib_doc_commus %>% 
  left_join(tib_doc_SHS,by="id_doc") 

Quelles sont les communautés d’auteur dans lesquelles ces termes sont les plus pregnants?

Code
tib_doc_commus_SHS_summary=tib_doc_commus_SHS %>% 
  group_by(community) %>% 
  summarise(ndoc=n(),.groups="drop")
ggplot(tib_doc_commus_SHS %>% filter(!is.na(community)),
       aes(x=community,y=SHS_spec,fill=community))+
  geom_boxplot()+
  scale_y_log10()+
  geom_text(data=tib_doc_commus_SHS_summary,
            aes(label=paste0("n=",ndoc),y=1),size=3)+
  theme(legend.position="none")

Code
ggplot(tib_doc_commus_SHS %>% na.omit(),
       aes(x=PY,y=SHS_spec))+
  geom_point(aes(col=community))+
  geom_smooth(method="lm",se=TRUE)+
  facet_wrap(facets=vars(community))+
  scale_x_continuous(limits=c(1990,2024))+
  scale_y_sqrt()

Identification de thématiques par la méthode de Reinert

Code
tib_doc_ABc=readRDS("data/tib_doc_ABW.RDS") %>% 
  group_by(id_doc) %>% 
  summarise(ABc=paste0(ABW,collapse=" "),.groups="drop")
corpus=quanteda::corpus(tib_doc_ABc,
                        docid_field="id_doc",text_field="ABc")
tok <- quanteda::tokens(corpus, remove_numbers = TRUE)
dtm <- quanteda::dfm(tok)
dtm2 <- quanteda::dfm_trim(dtm, min_docfreq = 10)
Code
set.seed(123)
library(rainette)
res=rainette(dtm2,k=20,min_split_members=20)
#rainette_explor(res,dtm=dtm2)
rainette_plot(
  res, dtm, k = 9,
  n_terms = 20,
  free_scales = TRUE,
  measure = "chi2",
  show_negative = FALSE,
  text_size = 10
)

Code
tib_classes_colors=tibble::tibble(
  class=paste0("class_",1:9),
  color=rainette:::groups_colors(9),
)

tib_doc_commus_SHS_classes=tibble::tibble(
  id_doc=names(corpus),
  class=paste0("class_",cutree_rainette(res, k = 9))
) %>% 
  mutate(class_name=case_when(class=="class_1"~"fine sediment",#river_management",
                            class=="class_2"~"organic matter",#glaciology",
                            class=="class_3"~"delta",#geophysics",
                            class=="class_4"~"population_genetics",#ecology_communities",
                            class=="class_5"~"ecology_habitat",#"ecology_fish",
                            class=="class_6"~"ecology_abundance",#geology",
                            class=="class_7"~"management",#"organic matter",
                            class=="class_8"~"glaciology",#"sediment",
                            class=="class_9"~"geology",#"delta",
                            is.na(class)~NA_character_)) %>% 
  left_join(tib_doc_commus_SHS,by="id_doc")

Nombre d’articles s’inscrivant dans les thématiques au cours du temps

Code
tib=tib_doc_commus_SHS_classes %>% 
  mutate(ntot=n()) %>% 
  group_by(PY) %>% 
  mutate(nPY=n()) %>% 
  ungroup() %>% 
  group_by(class,class_name) %>% 
  mutate(nclass=n()) %>% 
  ungroup() %>% 
  group_by(PY,nPY) %>% 
  mutate(propPY=nPY/ntot) %>%
  ungroup() %>% 
  group_by(class,class_name,nclass) %>% 
  mutate(propclass=nclass/ntot) %>% 
  ungroup() %>% 
  group_by(PY,propPY,propclass,ntot,class,class_name) %>% 
  summarise(n=n(),
            nprev=ntot*propPY*propclass,
            .groups="drop") %>% 
  unique()
ggplot(tib,aes(x=PY,y=n,fill=class))+ 
  geom_path(aes(x=PY,y=nprev))+
  geom_col(alpha=0.5)+
  facet_wrap(facets=vars(class_name))+
  scale_fill_manual(breaks=tib_classes_colors$class,
                    values=tib_classes_colors$color)

Croisement communautés d’auteurs et thématiques

Code
tib=tib_doc_commus_SHS_classes %>% 
  group_by(community,class,class_name) %>% 
  tally() %>% 
  na.omit()

ggplot(tib,aes(x=class_name,y=n,fill=class))+
  facet_wrap(facets=vars(community))+
  geom_col()+
  coord_flip()+
  theme(legend.position="none")+
  scale_fill_manual(breaks=tib_classes_colors$class,
                    values=tib_classes_colors$color)

Analyse des réseaux de mots-clés et de co-citation

Co-occurrence des mots-clés

Code
nw_cooc_kw <- biblioNetwork(M,
                           analysis = "co-occurrences",
                           network = "keywords",
                           sep = ";")
netstat <- networkStat(nw_cooc_kw)
Code
p=networkPlot(nw_cooc_kw,
                weighted=T, n = 150,
                Title = "Co-occurence des mots-clés",
                type = "fruchterman",
                cluster="louvain",
                size=T,
                edgesize = 5,
                labelsize=0.7)

Co-Citation

Code
M=M %>% 
  mutate(CR=str_replace(CR,"ANONYMOUS.*;","")) %>% 
  mutate(CR=str_replace(CR,"NO TITLE CAPTURED","")) 
nw_cocit_doc <- biblioNetwork(M,
                           analysis = "co-citation",
                           network = "references",
                           sep = ";")

p=networkPlot(nw_cocit_doc,
                n = 150,
                Title = "Co-Citation Network",
                type = "auto",
                size=T,
                cluster="louvain",
                remove.multiple=FALSE,
                labelsize=0.7,
                edgesize = 5,
                label.n=30)

Structure conceptuelle basée sur les mots-clés

Code
CS <- conceptualStructure(M,
                          field="ID",
                          method="MCA",
                          minDegree=10,
                          clust=8,
                          stemming=FALSE,
                          labelsize=10,
                          documents=3,
                          graph=FALSE)
CS$graph_terms

Code
CS$graph_dendrogram
NULL